﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using Physic = StillDesign.PhysX; //PhysX
using PhysicMath = StillDesign.PhysX.MathPrimitives; //PhysX

namespace Game
{
    /*-------------------------------------------------------------------------
    | PhysicScene:
    | ------------
    | Physic simulation scene
    |---------------------------------------------------------------------------*/
    public class PhysicScene : Scene
    {
        public enum SceneObjectGroupID : short
        {
            Static = 0,
            Dynamic = 1,
            Character = 2
        }
        private Physic.Core mPhyCore;
        private Physic.Scene mPhyScene;
        private Physic.ControllerManager mPhyControllerManager;
        private UserOutput mUserOutput;


        /*-------------------------------------------------------------------------
        | PhysicBody:
        | ------------
        | Physic simulation for body
        |---------------------------------------------------------------------------*/
        
        // MEM LEAK ON RUNTIME - CLEAN ONLY END APPLICATION
        class UserOutput : Physic.UserOutputStream
        {
            ~UserOutput()
            {
                //Log.Hint("UserOutput Destroy");
            }

            public override void Print(String message)
            {
                Log.Hint("PhysX: " + message);
            }
            public override Physic.AssertResponse ReportAssertionViolation(String message, String file, int lineNumber)
            {
                Log.Hint("PhysX: " + message);

                return Physic.AssertResponse.Continue;
            }
            public override void ReportError(Physic.ErrorCode errorCode, String message, String file, int lineNumber)
            {
                Log.Hint("PhysX: " + message);
            }
        }

        public PhysicScene(IDK.Engine engine)
            : base(engine)
        {
            InitPhysic();
        }

        protected void InitPhysic()
        {
            Log.Warn("PhysX :" + Physic.Core.SDKVersion);

            Physic.CoreDescription desc = new Physic.CoreDescription();
            mPhyCore = new Physic.Core(desc, mUserOutput = new UserOutput());

            mPhyCore.Parameters.SkinWidth = 0.0025f;
            mPhyCore.Foundation.RemoteDebugger.Connect("localhost");
            mPhyCore.Parameters.ContinuousCollisionDetection = true;
            mPhyCore.Parameters.ContinuousCollisionDetectionEpsilon = 0.01f;
          

            Physic.SceneDescription sceneDesc = new Physic.SceneDescription();
            sceneDesc.SetToDefault();

            //Trigger system
            var triggerEventSystem = new PhysicSceneTriggerEventSystem();
            TriggerEventSystem = triggerEventSystem;

            //sceneDesc.UpAxis = 3;
            sceneDesc.UserTriggerReport = triggerEventSystem;
            sceneDesc.Gravity = new PhysicMath.Vector3(0, -9.8f, 0);
            sceneDesc.GroundPlaneEnabled = false;


            mPhyScene = mPhyCore.CreateScene(sceneDesc);

            //mPhyScene.SetActorPairFlags(
            mPhyScene.SetActorGroupPairFlags(
                (short)SceneObjectGroupID.Dynamic, 
                (short)SceneObjectGroupID.Character,
                //Physic.ContactPairFlag.Forces
                Physic.ContactPairFlag.OnStartTouch
                | Physic.ContactPairFlag.Forces
                );

            var hitEventSystem = new PhysicSceneHitEventSystem();
            HitEventSystem = hitEventSystem;

            mPhyScene.UserContactReport = hitEventSystem;

            // default material
            mPhyScene.Materials[0].Restitution = 0.2f;
            mPhyScene.Materials[0].StaticFriction = 0.8f;
            mPhyScene.Materials[0].DynamicFriction = 0.8f;

            //controller
            mPhyControllerManager = mPhyScene.CreateControllerManager();
        }

        public IDK.Vector3 RayCast(IDK.Vector3 start, IDK.Vector3 direction)
        {
            Physic.RaycastHit[] result = mPhyScene.RaycastAllShapes(new Physic.Ray(IDKToPhysic.ToVector3(start), IDKToPhysic.ToVector3(direction)), Physic.ShapesType.All);
            if (result.Length > 0)
            {
                return IDKToPhysic.ToVector3(result[0].WorldImpact);
            }
            else
            {
                return IDK.Vector3.Zero;
            }
        }

        protected void PhysicFree()
        {
            //clean up
            TriggerEventSystem.Clear();
            TriggerEventSystem = null;

            HitEventSystem.Clear();
            HitEventSystem = null;

            mPhyCore.Foundation.RemoteDebugger.Disconnect();

            mPhyControllerManager.Dispose();
            mPhyControllerManager = null;

            mPhyScene.UserTriggerReport.Dispose();
            mPhyScene.UserTriggerReport = null;

            mPhyScene.Dispose();
            mPhyScene = null;

            mPhyCore.Dispose();
            mPhyCore = null;

            mUserOutput.Dispose();
            mUserOutput = null;
        }

        ~PhysicScene()
        {
            PhysicFree();
        }

        public Physic.TriangleMeshShapeDescription CreateTriangleMesh(IDK.IndexVertexBuffer meshData)
        {
            Physic.TriangleMeshShapeDescription shapeDesc = new Physic.TriangleMeshShapeDescription();

            // create descriptor for triangle mesh
            using (Physic.TriangleMeshDescription desc = new Physic.TriangleMeshDescription())
            {
                int vertCount = meshData.GetVertexCount();
                int triCount = meshData.GetIndexCount();
                int indexCount = triCount * 3;

                short[] indices = new short[indexCount];
                IDK.Vector3[] vertices = new IDK.Vector3[vertCount];

                meshData.GetIndexData(indices);
                meshData.GetVertexData(vertices);
               
                desc.Flags |= Physic.MeshFlag.Indices16Bit;

                desc.AllocateVertices<PhysicMath.Vector3>(vertCount);
                desc.AllocateTriangles<short>(triCount);

                desc.TriangleStream.SetData(indices);
                desc.VerticesStream.SetData(vertices);


                desc.VertexCount = vertCount;
                desc.TriangleCount = triCount;

      
                MemoryStream stream = new MemoryStream();

                Physic.Cooking.InitializeCooking();

                if (Physic.Cooking.CookTriangleMesh(desc, stream))
                {
                    stream.Seek(0, SeekOrigin.Begin);
                    shapeDesc.TriangleMesh = mPhyCore.CreateTriangleMesh(stream);
                    Physic.Cooking.CloseCooking();
                }
                else
                {
                    Log.Error("Unable load terrain");
                }
            }
            return shapeDesc;
        }

        //NOTE: todo refactor code move to loading state
        public void LoadCollisonMap(String name)
        {
            IDK.IndexVertexBuffer meshData = Core.LoadVertexIndexBuffer(@"Maps\" + name + @"\collision");

            // actor descriptor with collection of shapes
            Physic.ActorDescription actorDesc = new Physic.ActorDescription();

            //IDK.Mesh
            actorDesc.Shapes.Add(CreateTriangleMesh(meshData));

            Physic.Actor actor = mPhyScene.CreateActor(actorDesc);
        }

        public Body CreatePhysicBody(PhysicActorDescription constructor)
        {
            var pbody = new PhysicBody(constructor.SceneNode, mPhyScene.CreateActor(constructor.ActorDescription));
            return pbody;
        }

        public void DeleteBody(Body body)
        {
            //
            var pbody = body as PhysicBody;
            pbody.Actor.Dispose();
        }
 
        public PhysicCharacter AddPhysicCharacter(IDK.SceneNode node)
        {
            IDK.Vector3 pos = node.GetTransformation().GetWorldPosition();

            Physic.CapsuleControllerDescription desc = new Physic.CapsuleControllerDescription(1f, 1f);
            desc.SetToDefault();
            desc.Radius = 1.0f;
            desc.Height = 1.0f;


            desc.SkinWidth = 0.2f;
            //desc.SlopeLimit = 0;

            desc.StepOffset = 0.5f;
            desc.UpDirection = Physic.Axis.Y;
            desc.ClimbingMode = Physic.CapsuleClimbingMode.Easy;
            desc.Callback = new PhysicCharacter.Hit();

            Physic.CapsuleController controller = Controllers.CreateController<Physic.CapsuleController>(desc);
            controller.Actor.Name = node.GetName();
            //controller.Actor.Mass = 1;
            controller.Position = IDKToPhysic.ToVector3(pos);
            controller.SetCollisionEnabled(true);

            controller.Actor.Group = (short)PhysicScene.SceneObjectGroupID.Character; 

            PhysicCharacter newItem = new PhysicCharacter(controller, node);

            return newItem;
        }

        public override void OnUpdate(Events.ProcessSceneUpdate evnt)
        {
            base.OnUpdate(evnt);

            mPhyControllerManager.UpdateControllers();
            mPhyScene.Simulate(evnt.ElapsedTime);
            mPhyScene.FlushStream();
            mPhyScene.FetchResults(Physic.SimulationStatus.RigidBodyFinished, true);
        }

        protected Physic.ControllerManager Controllers
        {
            get { return mPhyControllerManager; }
        }
    }
}
